import {isDef} from "../../shared/util";
import {isRealNode, isSameNode, keyToIndex} from "./utils";
import {createElement} from "../../platform/web/patch/create-element";

export class PatchOption {
    /**
     * 创建一个 Patch 时的 option
     * @param {Array<Object>} modules
     * @param {Object} nodeOperations
     */
    constructor(modules, nodeOperations) {
        this.modules = modules;
        this.nodeOperations = nodeOperations;
    }
}

// 现在使用到的 hook
const PATCH_HOOK = ['prepatch', 'init', 'mount', 'update', 'destroy', 'afterpatch'];

export function createPatchFunction (options) {

    const modules = options.modules;
    const nodeOperations = options.nodeOperations;

    // 组装各个模块的 hook
    const hook = {};
    for (let i=0; i<PATCH_HOOK.length; i++) {
        const hookName = PATCH_HOOK[i];
        hook[hookName] = [];
    }
    for (let i=0; i<modules.length; i++) {
        const module = modules[i];
        for (let j=0; j<PATCH_HOOK.length; j++) {
            const hookName = PATCH_HOOK[j];
            if (isDef(module[hookName])) {
                const hookHandler = module[hookName];
                hook[hookName].push(hookHandler);
            }
        }
    }

    // ============================================================================
    // hook 触发方法
    // ============================================================================

    // fixme arguements design ?
    function invokePrepatchHook () {

    }

    // use
    function invokeMountHook (element, newNode) {
        const mountHookHandlers = hook['mount'];
        for (let i=0; i<mountHookHandlers.length; i++) {
            const handler = mountHookHandlers[i];
            handler(element, newNode);
        }
    }

    // use
    function invokeUpdateHook (oldNode, newNode) {
        const updateHookHandlers = hook['update'];
        for (let i=0; i<updateHookHandlers.length; i++) {
            const handler = updateHookHandlers[i];
            handler(oldNode, newNode);
        }
    }

    // use
    function invokeDestroyHook (oldNode) {
        const destroyHookHandlers = hook['destroy'];
        for (let i=0; i<destroyHookHandlers.length; i++) {
            const handler = destroyHookHandlers[i];
            handler(oldNode);
        }
    }

    // ============================================================================
    // Patch 方法
    // ============================================================================

    /**
     *
     * @param {Node | VNode} oldNode
     * @param {VNode} newNode
     */
     function patch (oldNode, newNode) {

        // 旧节点不存在，新节点不存在
        // 什么也不做
        if (!isDef(oldNode) && !isDef(newNode)) {
            return null;
        }
        // 旧节点存在， 新节点不存在
        // 旧节点需要删除
        if (isDef(oldNode) && !isDef(newNode)) {
            destroyDOM(oldNode, parent);
            return null;
        }

        // 这里分两种分析方法
        const parent = newNode.context.parent;

        // 旧节点不存在，新节点存在
        // 插入新节点
        if (!isDef(oldNode) && isDef(newNode)) {
            createDOM(newNode, parent, null);
        }

        // 旧节点存在，新节点存在
        // 判断两节点是否相同
        if (isDef(oldNode) && isDef(newNode)) {
            if (isSameNode(oldNode, newNode)) {
                patchNode(oldNode, newNode);
            }
            else {
                const refEle = isRealNode(oldNode) ? oldNode : oldNode.elem;
                createDOM(newNode, parent, refEle);
                destroyDOM(oldNode, parent);
            }
        }

        // 返回此次创建的 VNode 树根节点
        return newNode;
    }

    /**
     * 删除 vnode 对应的 DOM 节点
     * @param {Node | VNode} vnode
     * @param {Node} parent
     */
    function destroyDOM (vnode, parent) {
        // 获取要删除的 DOM 节点
        let removeElement = null;
        isRealNode(vnode)
            ? removeElement = vnode
            : removeElement = vnode.elem;
        // 调用 destroy 回调
        invokeDestroyHook(vnode);
        // 删除该 DOM 节点
        nodeOperations.removeChild(parent, removeElement);
        // 销毁组件真实模板
        if (vnode.isComponent) {
            const componentDestroyHook = vnode.data.destroy;
            componentDestroyHook(vnode);
        }
    }

    /**
     * 1. 为 vnode 节点创建真实 DOM 节点
     * 2. 根据 vnode 节点内容更新 DOM 节点
     * 3. 根据传入的 parent, refElement 判断如何插入真实 DOM 节点
     * @param {VNode} vnode
     * @param {Node} parent
     * @param {Node} refNode
     */
    function createDOM (vnode,parent,refNode) {
        // 创建对应标签的 DOM 节点， 但没有任何内容
        const element = createElement(vnode);
        vnode.elem = element;
        // 更新真实 DOM 内容
        invokeMountHook(element, vnode);

        // 如果 vnode 是组件占位节点, 调用组件 init 生命周期回调
        if (vnode.isComponent) {
            const componentInitHook = vnode.data.init;
            componentInitHook(vnode);
        }

        // 根据 parent 与 refElement 进行 DOM 操作
        if (isDef(refNode)) {
            const parent = nodeOperations.getParent(refNode);
            if (isDef(parent)) {
                nodeOperations.insertBefore(parent, element, refNode);
                // fixme 貌似是不需要这一行的, refEle 由 destroyDOM 进行处理
                // nodeOperations.removeChild(refNode);
            }
            else {
                console.warn("something wrong happen in patch");
            }
        }
        else if (isDef(parent)) {
            nodeOperations.append(element, parent);
        }
        // 创建孩子节点
        if (vnode.children && vnode.children.length !== 0) {
            const parent = vnode.elem;
            createChildren(vnode.children, parent);
        }

        // 检查是否是组件
        if (vnode.isComponent) {
            // 调用组件生命周期 mount
            const componentMountHook = vnode.data.mount;
            componentMountHook(vnode);
        }
    }

    /**
     * 更新该 VNode 的子节点
     * @param {Array<VNode>} children
     * @param {Node} parent
     */
    function createChildren (children, parent) {
        for (let i=0; i<children.length; i++) {
            createDOM(children[i], parent, null);
        }
    }

    /**
     * 两者是同一个节点，将两者的内容和孩子节点进行比较
     * @param {Node | VNode} oldNode
     * @param {VNode} newNode
     */
    function patchNode (oldNode, newNode) {
        // newNode 的 elem 指向已有的 DOM 节点
        isRealNode(oldNode) ? newNode.elem = oldNode : newNode.elem = oldNode.elem;
        // 组件更新方法
        if (newNode.isComponent) {
            // 调用组件生命周期 update
            const componentUpdateHook = newNode.data.update;
            componentUpdateHook(oldNode, newNode);
        }
        // 普通节点更新方法
        else {
            // 调用更新回调
            invokeUpdateHook(oldNode, newNode);
            // 更新孩子节点
            const parent = newNode.elem;
            const oldChildren = isRealNode(oldNode)
                ? [...oldNode.children]
                : oldNode.children;
            const newChildren = newNode.children;
            updateChildren(parent, oldChildren, newChildren);
        }
    }

    // ============================================================================
    // updateChildren 所需要的函数
    // ============================================================================

    /**
     * 在旧的孩子节点(start, finish)中寻找是否有与 node 相同的节点
     * @param {VNode} node
     * @param {Array<VNode | Node>} oldChildren
     * @param {Number} start
     * @param {Number} finish
     * @return {null|VNode|Node}
     */
    function findIdxInOld (node, oldChildren, start, finish) {
        for (let i=start; i<=finish; i++) {
            const oldNode = oldChildren[i];
            if (isSameNode(node, oldNode)) {
                return oldNode;
            }
        }
        return null;
    }

    /**
     * 删除多个 VNode 对应的 DOM 节点
     * @param {Array<VNode|Node>} children
     * @param {Number} start
     * @param {Number} finish
     * @param {Node} parent
     */
    function removeNodes (children, start, finish, parent) {
        for (let i=start; i<=finish; i++) {
            destroyDOM(children[i], parent);
        }
    }

    /**
     * 添加多个 VNode 对应的 DOM 节点
     * @param {Array<VNode>} children
     * @param {Number} start
     * @param {Number} finish
     * @param {Node} parent
     * @param {Node} refEle
     */
    function addNodes (children, start, finish, parent, refEle) {
        for (let i=start; i<=finish; i++) {
            createDOM(children[i], parent, refEle);
        }
    }

    /**
     * 比较孩子节点
     * @param {Node} parent
     * @param {Array<Node|VNode>} oldChildren
     * @param {Array<VNode>} newChildren
     */
    function updateChildren (parent, oldChildren, newChildren) {
        if (oldChildren.length === 0 && newChildren === 0) {
            return;
        }

        let oldStartIndex = 0;
        let oldStartNode = oldChildren[oldStartIndex];
        let oldFinishIndex = oldChildren.length - 1;
        let oldFinishNode = oldChildren[oldFinishIndex];
        let newStartIndex = 0;
        let newStartNode = newChildren[newStartIndex];
        let newFinishIndex = newChildren.length - 1;
        let newFinishNode = newChildren[newFinishIndex];

        let keyToIndexOld = null;
        while (oldStartIndex <= oldFinishIndex && newStartIndex <= newFinishIndex) {
            if (!isDef(oldStartNode)) {
                oldStartIndex ++;
                oldStartNode = oldChildren[oldStartIndex];
            }
            else if (!isDef(oldFinishNode)) {
                oldFinishIndex --;
                oldFinishNode = oldChildren[oldFinishIndex];
            }
            else if (!isDef(newStartNode)) {
                newStartIndex ++;
                newStartNode = newChildren[newStartIndex];
            }
            else if (!isDef(newFinishNode)) {
                newFinishIndex --;
                newFinishNode = newChildren[newFinishIndex];
            }
            // 比较 oldStartNode, newStartNode 直到不同为止
            else if (isSameNode(oldStartNode, newStartNode)) {
                patchNode(oldStartNode, newStartNode);
                oldStartNode = oldChildren[++oldStartIndex];
                newStartNode = newChildren[++newStartIndex];
            }
            // 比较 oldFinishNode, newFinishNode 直到不同为止
            else if (isSameNode(oldFinishNode, newFinishNode)) {
                patchNode(oldFinishNode, newFinishNode);
                oldFinishNode = oldChildren[--oldFinishIndex];
                newFinishNode = newChildren[--newFinishIndex];
            }
            // 比较 oldStartNode, newFinishNode 并进行交换直到不同为止
            else if (isSameNode(oldStartNode, newFinishNode)) {
                patchNode(oldStartNode, newFinishNode);
                // DOM 操作
                const child = isRealNode(oldStartNode) ? oldStartNode : oldStartNode.elem;
                const refEle = isRealNode(oldFinishNode) ? oldFinishNode.nextSibling : oldFinishNode.elem.nextSibling;
                nodeOperations.insertBefore(parent, child, refEle);
                // 获取新的 VNode
                oldStartNode = oldChildren[oldStartIndex];
                oldFinishNode = oldChildren[--oldFinishIndex];
                newFinishNode = newChildren[--newFinishIndex];
            }
            // 比较 oldFinishNode, newStartNode 并进行交换直到不同为止
            else if (isSameNode(oldFinishNode, newStartNode)) {
                patchNode(oldFinishNode, newStartNode);
                // DOM 操作
                const child = isRealNode(oldFinishNode) ? oldFinishNode : oldFinishNode.elem;
                const refEle = isRealNode(oldStartNode) ? oldStartNode : oldStartNode.elem;
                nodeOperations.insertBefore(parent, child, refEle);
                // 获取新的 VNode
                oldFinishNode = oldChildren[oldFinishIndex];
                oldStartNode = oldChildren[++oldStartIndex];
                newStartNode = newChildren[++newStartIndex];
            }
            else {
                // 根据 key 进行比对修改
                keyToIndexOld = keyToIndex(oldChildren, oldStartIndex, oldFinishIndex);
                const target = isDef(newStartNode.key)
                    ? keyToIndexOld[newStartNode.key]
                    : findIdxInOld(newStartNode, oldChildren, oldStartIndex, oldFinishIndex);
                // 如果没有对应可以复用的 DOM 节点，则新增 newStartNode 对应的 DOM 节点
                if (!isDef(target)) {
                    const refEle = isRealNode(oldStartNode) ? oldStartNode : oldStartNode.elem;
                    createDOM(newStartNode, parent, refEle);
                    // 获取新的 VNode
                    oldStartIndex ++;
                    newStartNode = newChildren[++newStartIndex];
                }
                // 有对应的可以复用的 DOM 节点，进行检查
                else {
                    // 如果是同一节点， 可以复用
                    if (isSameNode(target, newStartNode)) {
                        patchNode(target, newStartNode);
                        // DOM 操作
                        const child = isRealNode(target) ? target : target.elem;
                        const refEle = isRealNode(oldStartNode) ? oldStartNode : oldStartNode.elem;
                        nodeOperations.insertBefore(parent, child, refEle);
                        // 获取新的 VNode
                        oldStartIndex ++;
                        newStartNode = newChildren[++newStartIndex];
                    }
                        // 不是同一节点，不可以复用
                        // 1. key 都为 undefined (没有标记 key 的节点)
                    // 2. key 相同但是其他属性发生了变化，导致节点需要重新渲染
                    else {
                        // 创建新节点对应的 DOM 节点
                        const refEle = isRealNode(oldStartNode) ? oldStartNode : oldStartNode.elem;
                        createDOM(newStartNode, parent, refEle);
                        // 获取新的 VNode
                        oldStartIndex ++;
                        newStartNode = newChildren[++newStartIndex];
                    }
                }
            }
        }
        // 如果旧节点没有遍历完,则删除剩下的旧节点
        if (oldStartIndex <= oldFinishIndex) {
            removeNodes(oldChildren, oldStartIndex, oldFinishIndex, parent);
        }
        // 如果新节点没有遍历完，则添加所有的新节点
        if (newStartIndex <= newFinishIndex) {
            const oldStartNode = oldChildren[oldStartIndex];
            let refEle = null;
            if (isDef(oldStartNode)) {
                refEle = isRealNode(oldStartNode) ? oldStartNode : oldStartNode.elem;
            }
            addNodes(newChildren, newStartIndex, newFinishIndex, parent, refEle);
        }
    }


    // ============================================================================
    // 返回组装好的 patch 方法
    // ============================================================================

    // 创建一个 patch 函数
    const __patch__ = function (oldNode, newNode) {
        patch(oldNode, newNode);
        return newNode;
    };

    // 返回 patch 函数
    return __patch__;
}

